package com.wicketFlowTester.framework;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.wicket.Component;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.Page;
import org.apache.wicket.behavior.AbstractAjaxBehavior;
import org.apache.wicket.protocol.http.WebApplication;
import org.apache.wicket.protocol.http.mock.MockHttpServletRequest;
import org.apache.wicket.util.lang.Classes;
import org.apache.wicket.util.tester.WicketTester;
import org.apache.wicket.util.visit.IVisit;
import org.apache.wicket.util.visit.IVisitor;

import com.wicketFlowTester.assertion.Attest;

public class WicketTesterFacadeImpl implements WicketTesterFacade{

    final WicketTester wicketTester;
    
    private WicketTesterFacadeImpl() {
        wicketTester = new WicketTester();
    }
    
    private WicketTesterFacadeImpl(WebApplication webApplication) {
        wicketTester = new WicketTester(webApplication);
    }
    
    public static WicketTesterFacade getInstance(WebApplication webApplication) {
        return new WicketTesterFacadeImpl(webApplication);
    }
    
    public static WicketTesterFacade getInstance() {
        return new WicketTesterFacadeImpl();
    }

    public Page startPage(Page page) {
        return wicketTester.startPage(page);
    }

    public Page getLastRenderedPage() {
        return wicketTester.getLastRenderedPage();
    }

    public void executeAjaxEvent(Component component, String event) {
        wicketTester.executeAjaxEvent(component, event);
    }

    public Component getComponentFromLastRenderedPage(final String path) {
        // first check if we can find the component with the specified path - if
        // not, check if its an abbreviated path
        final String fullPath = lookupPath(getLastRenderedPage(), path);
        if (fullPath == null) {
            return null;
        }

        return wicketTester.getComponentFromLastRenderedPage(fullPath);
    }

    public FormTesterFacade newFormTester(String path) {
        return new FormTesterFacadeImpl(wicketTester.newFormTester(path));
    }

    private String lookupPath(final MarkupContainer markupContainer, final String path) {
        // try to look it up directly
        if (markupContainer.get(path) != null) {
            return path;
        }

        // if that fails, traverse the component hierarchy looking for it
        final List<Component> candidates = new ArrayList<Component>();
        markupContainer.visitChildren(new IVisitor<Component, Void>() {
            private Set<Component> visited = new HashSet<Component>();

            public void component(Component c, IVisit<Void> visit) {
                if (!visited.contains(c)) {
                    visited.add(c);

                    if (c.getId().equals(path)) {
                        candidates.add(c);
                    }
                }
            }
        });
        // if its unambiguous, then return the full path
        if (candidates.isEmpty()) {
            Attest.fail("path: '" + path + "' not found for " + Classes.simpleName(markupContainer.getClass()));
            return null;
        } else if (candidates.size() == 1) {
            final String pathToContainer = markupContainer.getPath();
            final String pathToComponent = candidates.get(0).getPath();
            return pathToComponent.replaceFirst(pathToContainer + ":", "");
        } else {
            String message = "path: '" + path + "' is ambiguous for " + Classes.simpleName(markupContainer.getClass())
                    + ". Possible candidates are: ";
            for (final Component c : candidates) {
                message += "[" + c.getPath() + "]";
            }
            Attest.fail(message);
            return null;
        }
    }

    public void executeBehavior(AbstractAjaxBehavior behavior) {
        wicketTester.executeBehavior(behavior);
    }

    public MockHttpServletRequest getRequest() {
        return wicketTester.getRequest();
    }
}
